package com.hqd.ch03.v31.web.servlet.handler;

import com.hqd.ch03.utils.ClassUtils;
import com.hqd.ch03.utils.LinkedMultiValueMap;
import com.hqd.ch03.utils.MultiValueMap;
import com.hqd.ch03.v31.core.MethodIntrospector;
import com.hqd.ch03.v31.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping {
    private final MappingRegistry mappingRegistry = new MappingRegistry();

    /**
     * 解析所有方法
     *
     * @param handler
     */
    protected void detectHandlerMethods(Object handler) {
        Class<?> handlerType = handler.getClass();
        if (handlerType != null) {
            Class<?> userType = ClassUtils.getUserClass(handlerType);
            //扫描所有方法
            Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                    (MethodIntrospector.MetadataLookup<T>) method -> {
                        try {
                            //抽象方法，具体有子类实现
                            return getMappingForMethod(method, userType);
                        } catch (Throwable ex) {
                            throw new IllegalStateException("Invalid mapping on handler class [" +
                                    userType.getName() + "]: " + method, ex);
                        }
                    });

            //注册所有方法
            methods.forEach((method, mapping) -> {
                registerHandlerMethod(handler, method, mapping);
            });
        }
    }

    protected void registerHandlerMethod(Object handler, Method method, T mapping) {
        this.mappingRegistry.register(mapping, handler, method);
    }

    protected abstract T getMappingForMethod(Method method, Class<?> handlerType);

    @Deprecated
    protected Set<String> getDirectPaths(T mapping) {
        Set<String> urls = Collections.emptySet();
        for (String path : getMappingPathPatterns(mapping)) {
            urls = (urls.isEmpty() ? new HashSet<>(1) : urls);
            urls.add(path);
        }
        return urls;
    }

    protected Set<String> getMappingPathPatterns(T mapping) {
        return Collections.emptySet();
    }

    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        String lookupPath = initLookupPath(request);
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }

    protected String initLookupPath(HttpServletRequest request) {
        return getUrlPathHelper().resolveAndCacheLookupPath(request);
    }

    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
        }
        if (!matches.isEmpty()) {
            /**
             * TODO：这里涉及到通配符问题，完全匹配的话应该不会出现多个的情况
             */
            Match bestMatch = matches.get(0);
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.getHandlerMethod();
        } else {
            return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
        }
    }

    protected void handleMatch(T mapping, String lookupPath, HttpServletRequest request) {
        request.setAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);
    }

    protected HandlerMethod handleNoMatch(Set<T> mappings, String lookupPath, HttpServletRequest request)
            throws Exception {
        return null;
    }

    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
        for (T mapping : mappings) {
            T match = getMatchingMapping(mapping, request);
            if (match != null) {
                matches.add(new Match(match, this.mappingRegistry.getRegistrations().get(mapping)));
            }
        }
    }

    protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);

    protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);

    /**
     * 控制器信息
     *
     * @param <T>
     */
    static class MappingRegistration<T> {
        //控制器
        private final T mapping;
        //处理方法
        private final HandlerMethod handlerMethod;
        //映射路径
        private final Set<String> directPaths;

        public MappingRegistration(T mapping, HandlerMethod handlerMethod,
                                   Set<String> directPaths) {
            this.mapping = mapping;
            this.handlerMethod = handlerMethod;
            this.directPaths = (directPaths != null ? directPaths : Collections.emptySet());
        }

        public T getMapping() {
            return this.mapping;
        }

        public HandlerMethod getHandlerMethod() {
            return this.handlerMethod;
        }

        public Set<String> getDirectPaths() {
            return this.directPaths;
        }
    }

    /**
     * 注册器
     */
    class MappingRegistry {

        /**
         * 控制器对象和控制器信息
         */
        private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
        /**
         * url和控制器
         */
        private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

        public Map<T, MappingRegistration<T>> getRegistrations() {
            return this.registry;
        }

        public List<T> getMappingsByDirectPath(String urlPath) {
            return this.pathLookup.get(urlPath);
        }

        public void register(T mapping, Object handler, Method method) {
            HandlerMethod handlerMethod = createHandlerMethod(handler, method);
            /**
             * 注册对应url
             */
            Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
            for (String path : directPaths) {
                this.pathLookup.add(path, mapping);
            }

            this.registry.put(mapping,
                    new MappingRegistration<>(mapping, handlerMethod, directPaths));
        }

        protected HandlerMethod createHandlerMethod(Object handler, Method method) {
            return new HandlerMethod(handler, method);
        }

        public void unregister(T mapping) {
            MappingRegistration<T> registration = this.registry.remove(mapping);
            if (registration == null) {
                return;
            }

            for (String path : registration.getDirectPaths()) {
                List<T> mappings = this.pathLookup.get(path);
                if (mappings != null) {
                    mappings.remove(registration.getMapping());
                    if (mappings.isEmpty()) {
                        this.pathLookup.remove(path);
                    }
                }
            }
        }

    }

    /**
     * 匹配信息
     */
    private class Match {
        //控制器
        private final T mapping;
        //控制器信息
        private final MappingRegistration<T> registration;

        public Match(T mapping, MappingRegistration<T> registration) {
            this.mapping = mapping;
            this.registration = registration;
        }

        public HandlerMethod getHandlerMethod() {
            return this.registration.getHandlerMethod();
        }
    }
}
